﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SeaBattle.Domain.Entities
{
    public class Field
    {
        private List<Point> points = new List<Point>();
        private List<Point> hits = new List<Point>();
        public class Point{
            public int X { get; set; }
            public int Y { get; set; }
        }
        public int Size { get;private set; }
        private FieldType[,] field;

        public FieldType[,] AField { get { return field; }}

        public Field(int size)
        {
            Size = size;
            field = new FieldType[size, size];
        }

        private void SetNoAcsess(int x, int y, FieldType type)
        {
            
            points.Add(new Point() { X = x, Y = y });
            int minRowIndex, maxRowIndex, minColumnIndex, maxColumnIndex;

            maxRowIndex = Math.Min(x + 1, Size - 1);
            minRowIndex = Math.Max(0, x - 1);
            maxColumnIndex = Math.Min(Size - 1, y + 1);
            minColumnIndex = Math.Max(0, y - 1);

            for (int i = minRowIndex; i <= maxRowIndex; i++)
                for (int j = minColumnIndex; j <= maxColumnIndex; j++)
                {
                    {
                        if (points.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                    }
                    if (field[i, j] != type)
                        field[i, j] = FieldType.TYPENOACSESS;
                    if (field[i, j] == type)
                        SetNoAcsess(i, j, type);
                }
        }
        private void Reset()
        {
            for (int i = 0; i < Size; i++)
                for (int j = 0; j < Size; j++)
                    if (field[i, j] == FieldType.TYPEACSESS || field[i,j] == FieldType.TYPECLOSEDACSESS)
                        field[i, j] = FieldType.TYPENONE;
        }
        
        public FieldType[,] ChekingOne()
        {            
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    if (field[i, j] == FieldType.TYPENONE)
                    {
                        field[i, j] = FieldType.TYPEACSESS;
                    }
                }
            }
            return field;
        }
        public bool SettingOne(int x, int y)
        {
            if (field[x, y] != FieldType.TYPEACSESS)
            {
                throw new Exception("Error, uncorrect cells");                    
            }
            field[x, y] = FieldType.TYPEONE;
            SetNoAcsess(x, y,FieldType.TYPEONE);
            Reset();
            return true;
        }

        private void SetAcsessHorizontal(FieldType type, int x, int y, FieldType[,] field)
        {
            {
                if (y == 8)
                {
                    ;
                }
            }
            if (field[x, y] != type)
                throw new Exception("Cells must be typing");
            int k = 0;
            int left = y, right = y;
            while (field[x, left] == type)
            {
                left--;
                if (left < 0)
                {
                    left = 0;
                    break;
                }
            }
            if (field[x, left] != type) left++;
            while (field[x, right] == type)
            {
                right++;
                if (right == Size)
                {
                    right--;
                    break;
                }
            }
            if (field[x, right] != type) right--;
            var k2 = k = right - left + 1;
            for (int j = Math.Max(0, left - ((int)type - k2)); j <= Math.Min(Size - 1, right + ((int)type - k2)); j++)
            {
                if (field[x, j] == FieldType.TYPENONE) field[x, j] = FieldType.TYPECLOSEDACSESS;
            }
            if (left > 0 && field[x, left - 1] == FieldType.TYPECLOSEDACSESS) field[x, left - 1] = FieldType.TYPEACSESS;
            if (right < Size - 1 && field[x, right + 1] == FieldType.TYPECLOSEDACSESS) field[x, right + 1] = FieldType.TYPEACSESS;
        }

        private void SetAcsessVertical(FieldType type, int x, int y, FieldType[,] field)
        {
            if (field[x, y] != type)
                throw new Exception("Cells must be typing");
            int k = 0;
            int top = x, bottom = x;
            while (field[top, y] == type)
            {
                top--;
                if (top < 0)
                {
                    top = 0;
                    break;
                }
            }
            if (field[top, y] != type) top++;
            while (field[bottom, y] == type)
            {
                bottom++;
                if (bottom == Size)
                {
                    bottom--;
                    break;
                }
            }
            if (field[bottom, y] != type) bottom--;
            var k2 = k = bottom - top + 1;
            for (int i = Math.Max(0, top - ((int)type - k2)); i <= Math.Min(Size - 1, bottom + ((int)type - k2)); i++)
            {
                if (field[i, y] == FieldType.TYPENONE) field[i, y] = FieldType.TYPECLOSEDACSESS;
            }
            if (top > 0 && field[top - 1, y] == FieldType.TYPECLOSEDACSESS) field[top -1 , y] = FieldType.TYPEACSESS;
            if (bottom < Size-1 && field[bottom + 1, y] == FieldType.TYPECLOSEDACSESS) field[bottom+1, y] = FieldType.TYPEACSESS;
        }

        private int CountingKVertical(FieldType type, int x, int y, FieldType[,] field)
        {
            int k = 0;
            if (field[x, y] == FieldType.TYPENONE)
            {
                for (int i = Math.Max(0, x - (int)type + 1); i <= Math.Min(Size - 1, x - 1 + (int)type); i++)
                {
                    if (field[i, y] == FieldType.TYPENONE) k++;
                }
            }
            else if (field[x, y] == type)
            {
                int top = x, bottom = x;
                while (field[top, y] == type)
                {
                    top--;
                    if (top < 0)
                    {
                        top = 0;
                        break;
                    }
                }
                if (field[top, y] != type) top++;
                while (field[bottom, y] == type)
                {
                    bottom++;
                    if (bottom == Size)
                    {
                        bottom--;
                        break;
                    }
                }
                if (field[bottom, y] != type) bottom--;
                var k2 = k = bottom - top + 1;
                for (int i = Math.Max(0, top - ((int)type - k2)); i <= Math.Min(Size - 1, bottom + ((int)type - k2)); i++)
                {
                    if (field[i, y] == FieldType.TYPENONE || field[i, y] == FieldType.TYPEACSESS) k++;
                }
            }
            return k;
        }

        private int CountingKHorizontal(FieldType type, int x, int y, FieldType[,] field)
        {
            int k = 0;
            if (field[x, y] == FieldType.TYPENONE)
            {
                for (int j = Math.Max(0, y - (int)type + 1); j <= Math.Min(Size - 1, y - 1 + (int)type); j++)
                {
                    if (field[x, j] == FieldType.TYPENONE || field[x,j] == FieldType.TYPEACSESS) k++;
                }
            }
            if (field[x, y] == type)
            {
                int left = y, right = y;
                while (field[x, left] == type)
                {
                    left--;
                    if (left < 0)
                    {
                        left = 0;
                        break;
                    }
                }
                if (field[x, left] != type) left++;
                while (field[x, right] == type)
                {
                    right++;
                    if (right == Size)
                    {
                        right--;
                        break;
                    }
                }
                if (field[x, right] != type) right--;
                var k2 = k = right - left + 1;
                for (int j = Math.Max(0, left - ((int)type - k2)); j <= Math.Min(Size - 1, right + ((int)type - k2)); j++)
                {
                    if (field[x, j] == FieldType.TYPENONE) k++;
                }
            }
            return k;
        }

        public FieldType[,] ChekingTwo(int counter)
        {
            int k = 0;
            var tmpField = new FieldType[Size, Size];
            Array.Copy(field, tmpField, Size * Size);
            for (int i = 0; i < Size; i++)
                for (int j = 0; j < Size; j++)
                {
                    if (counter == 0)
                    {
                        if (field[i, j] == FieldType.TYPENONE)
                        {
                            {
                                if (i == 3 && j == 9)
                                {
                                    ;
                                }
                                if ((i == 9 && j == 9))
                                {
                                    ;
                                }
                            }

                            k = CountingKHorizontal(FieldType.TYPETWO, i, j, tmpField);
                            if (k >= 2) { field[i, j] = FieldType.TYPEACSESS; continue; }
                            k = CountingKVertical(FieldType.TYPETWO, i, j, tmpField);
                            if (k >= 2) { field[i, j] = FieldType.TYPEACSESS; continue; }
                        }
                    }
                    else if (counter == 1)
                    {
                        if (field[i, j] != FieldType.TYPETWO) continue;
                        if (points.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                            SetAcsessHorizontal(FieldType.TYPETWO, i, j, field);
                        SetAcsessVertical(FieldType.TYPETWO, i, j, field);
                        return field;
                    }
                    else
                        throw new Exception("twoParts Ship have only two part!");
                }

            return field;
        }
        public bool SettingTwo(int x, int y, int counter)
        {
            if (field[x, y] != FieldType.TYPEACSESS)
            {
                throw new Exception("Error, uncorrect cells");
            }
            field[x, y] = FieldType.TYPETWO;
            Reset();
            if (counter == 1)
            {
                SetNoAcsess(x,y,FieldType.TYPETWO);
                return true;
            }
            return false;
        }

        public FieldType[,] ChekingThree(int counter)
        {
            int k = 0;
            var tmpField = new FieldType[Size, Size];
            Array.Copy(field, tmpField, Size * Size);
            for (int i = 0; i < Size; i++)
                for (int j = 0; j < Size; j++)
                {
                    if (counter == 0)
                    {
                        if (field[i, j] == FieldType.TYPENONE)
                        {
                            k = CountingKHorizontal(FieldType.TYPETHREE, i, j, tmpField);
                            if (k >= 3) { field[i, j] = FieldType.TYPEACSESS; continue; }
                            k = CountingKVertical(FieldType.TYPETHREE, i, j, tmpField);
                            if (k >= 3) { field[i, j] = FieldType.TYPEACSESS; continue; }
                        }
                    }
                    else if (counter == 1)
                    {                        
                        if (field[i, j] != FieldType.TYPETHREE) continue;
                        if (points.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                        k = CountingKHorizontal(FieldType.TYPETHREE, i, j, tmpField);
                        if (k >= 3)
                            SetAcsessHorizontal(FieldType.TYPETHREE, i, j, field);
                        k = CountingKVertical(FieldType.TYPETHREE, i, j, tmpField);
                        if (k >= 3)
                        SetAcsessVertical(FieldType.TYPETHREE, i, j, field);
                        return field;
                    }
                    else if (counter == 2)
                    {
                        if (field[i, j] != FieldType.TYPETHREE) continue;
                        if (points.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) { continue; }
                        {
                            if ((j > 0 && field[i, j - 1] == FieldType.TYPETHREE) || (j < Size - 1 && field[i, j + 1] == FieldType.TYPETHREE))
                            {
                                SetAcsessHorizontal(FieldType.TYPETHREE, i, j, field);
                            }
                            else if ( (i > 0 && field[i-1,j] == FieldType.TYPETHREE) || (i < Size-1 && field[i+1,j] == FieldType.TYPETHREE))
                                SetAcsessVertical(FieldType.TYPETHREE, i, j, field);
                        }
                    }
                    else
                        throw new Exception("Count of Part bigger than 3");

                }

            return field;
        }
        public bool SettingThree(int x, int y, int counter)
        {
            if (field[x, y] != FieldType.TYPEACSESS)
            {
                throw new Exception("Error, uncorrect cells");
            }
            field[x, y] = FieldType.TYPETHREE;
            Reset();
            if (counter == 2)
            {
                SetNoAcsess(x, y, FieldType.TYPETHREE);
                return true;
            }

            return false;
        }

        public FieldType[,] ChekingFour(int counter)
        {
            int k = 0;
            var tmpField = new FieldType[Size, Size];
            Array.Copy(field, tmpField, Size * Size);
            for (int i = 0; i < Size; i++)
                for (int j = 0; j < Size; j++)
                {
                    if (counter == 0)
                    {
                        if (field[i, j] == FieldType.TYPENONE)
                        {
                            k = CountingKHorizontal(FieldType.TYPEFOUR, i, j, tmpField);
                            if (k >= 4) { field[i, j] = FieldType.TYPEACSESS; continue; }
                            k = CountingKVertical(FieldType.TYPEFOUR, i, j, tmpField);
                            if (k >= 4) { field[i, j] = FieldType.TYPEACSESS; continue; }
                        }
                    }
                    else  if (counter == 1)
                    {
                        if (field[i, j] != FieldType.TYPEFOUR) continue;
                        if (points.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                        k = CountingKHorizontal(FieldType.TYPEFOUR, i, j, tmpField);
                        if (k >= 4)
                            SetAcsessHorizontal(FieldType.TYPEFOUR, i, j, field);
                        k = CountingKVertical(FieldType.TYPEFOUR, i, j, tmpField);
                        if (k >= 4)
                            SetAcsessVertical(FieldType.TYPEFOUR, i, j, field);
                        return field;
                    }
                   else if (counter == 2 || counter == 3)
                    {
                        if (field[i, j] != FieldType.TYPEFOUR) continue;
                        if (points.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                        {
                            if ((j > 0 && field[i, j - 1] == FieldType.TYPEFOUR) || (j < Size - 1 && field[i, j + 1] == FieldType.TYPEFOUR))
                            {
                                SetAcsessHorizontal(FieldType.TYPEFOUR, i, j, field);
                            }
                            else SetAcsessVertical(FieldType.TYPEFOUR, i, j, field);
                        }
                        return field;
                    }
                    else
                        throw new Exception("Count of Part bigger than 3");
                }

            return field;
        }
        public bool SettingFour(int x, int y, int counter)
        {
            if (field[x, y] != FieldType.TYPEACSESS)
            {
                throw new Exception("Error, uncorrect cells");
            }
            field[x, y] = FieldType.TYPEFOUR;
            Reset();
            if (counter == 3)
            {
                SetNoAcsess(x, y, FieldType.TYPEFOUR);
                return true;
            }

            return false;
        }


        internal void ResetField()
        {
            points.Clear();
            for (int i = 0; i < Size; i++)
                for (int j = 0; j < Size; j++)
                    field[i, j] = FieldType.TYPENONE;
        }
    
        public bool Shot(Field virtualField,int x, int y)
        {
            var point = points.FirstOrDefault(t => (t.X == x && t.Y == y));
            if (point == null)
            {
                virtualField.AField[x, y] = FieldType.TYPEMISS;
                return false;
            }
            virtualField.AField[x, y] = FieldType.TYPEHIT;
            field[x, y] = FieldType.TYPEHIT;
            points.Remove(point);
            field[x, y] = FieldType.TYPEHIT;
            return true;
        }

        public bool IsKill(int x, int y,List<Point> hits)
        {
            if (hits == null) hits = new List<Point>();
            hits.Add(new Point { X = x, Y = y });

            int minRowIndex, maxRowIndex, minColumnIndex, maxColumnIndex;

            maxRowIndex = Math.Min(x + 1, Size - 1);
            minRowIndex = Math.Max(0, x - 1);
            maxColumnIndex = Math.Min(Size - 1, y + 1);
            minColumnIndex = Math.Max(0, y - 1);

            for (int i = minRowIndex; i <= maxRowIndex; i++)
                for (int j = minColumnIndex; j <= maxColumnIndex; j++)
                {
                    if (hits.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                    if (IsShipPart(field[i, j]))
                        return false;
                    if (field[i, j] == FieldType.TYPEHIT)
                        if (!IsKill(i, j, hits))
                            return false;                    
                }
            return true;
        }
                
        private bool IsShipPart(FieldType cell)
        {
            if (cell >= FieldType.TYPEONE &&
                cell <= FieldType.TYPEFOUR)
                return true;
            return false;
        }

        public void SetPlayNoAcsess(Field virtualField, int x, int y)
        {
            hits.Add(new Point() { X = x, Y = y });
            int minRowIndex, maxRowIndex, minColumnIndex, maxColumnIndex;

            maxRowIndex = Math.Min(x + 1, Size - 1);
            minRowIndex = Math.Max(0, x - 1);
            maxColumnIndex = Math.Min(Size - 1, y + 1);
            minColumnIndex = Math.Max(0, y - 1);

            for (int i = minRowIndex; i <= maxRowIndex; i++)
                for (int j = minColumnIndex; j <= maxColumnIndex; j++)
                {   
                    if (hits.FirstOrDefault(t => (t.X == i && t.Y == j)) != null) continue;
                    if (field[i, j] != FieldType.TYPEHIT)
                    {
                        virtualField.AField[i, j] = FieldType.TYPENOACSESS;

                    }
                    if (field[i, j] == FieldType.TYPEHIT)
                        SetPlayNoAcsess(virtualField,i, j);
                }
        }

        public bool IsEnd()
        {
            if (points.Count == 0)
                return true;
            return false;
        }
    }


    public enum FieldType
    {
        TYPECLOSEDACSESS = -3,
        TYPENOACSESS = -2,
        TYPEACSESS,
        TYPENONE = 0,
        TYPEONE,
        TYPETWO,
        TYPETHREE,
        TYPEFOUR,
        TYPEFIVE,
        TYPESIX,
        TYPEMISS,
        TYPEHIT
    }
}
